/*
 ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1

 * The contents of this file are subject to the Mozilla Public License Version 
 * 1.1 (the "License"); you may not use this file except in compliance with 
 * the License. You may obtain a copy of the License at 
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Blazing Games Inc. (Billy D. Spelchan) code.
 *
 * The Initial Developer of the Original Code is
 * Blazing Games Inc.
 * Portions created by the Initial Developer are Copyright (C) 1999, 2003, 2005-2006
 * the Initial Developer. All Rights Reserved.
 * 
 * Contributor(s):
 *
 ***** END LICENSE BLOCK *****
 */
package com.blazinggames.holeman;

import java.applet.AudioClip;

public class Player
extends Sprite {
// *** CONSTANTS ***
  // Command codes
  public final static int CMD_LEFT = 1;
  public final static int CMD_RIGHT = 2;
  public final static int CMD_CLIMB_UP = 4;
  public final static int CMD_CLIMB_DOWN = 8;
  public final static int CMD_HOLE_LEFT = 16;
  public final static int CMD_HOLE_RIGHT = 32;
  public final static int CMD_RESTART = 64;
  // Commands - cheats
  public final static int CMD_CHEAT = 0x10000;
  public final static int CMD_CHEAT_LIFE = 0x10001;

  public final static int RESULT_NORMAL = 0;
  public final static int RESULT_RESET = 1;
  public final static int RESULT_DIE = 2;

  // Internal movement modes
  private final static int MOVE_NONE = 0;
  private final static int MOVE_LEFT = 1;
  private final static int MOVE_RIGHT = 2;
  private final static int MOVE_SLOWLEFT = 3;
  private final static int MOVE_SLOWRIGHT = 4;
  private final static int MOVE_CHAINLEFT = 5;
  private final static int MOVE_CHAINRIGHT = 6;
  private final static int MOVE_UPLADDER = 7;
  private final static int MOVE_DOWNLADDER = 8;
  private final static int MOVE_UPROPE = 9;
  private final static int MOVE_DOWNROPE = 10;
  private final static int MOVE_FALL = 11;
  private final static int MOVE_GETHOLELEFT = 12;
  private final static int MOVE_GETHOLERIGHT = 13;
//  private final static int MOVE_MAKEHOLELEFT = 14;
//  private final static int MOVE_MAKEHOLERIGHT = 15;
  private final static int MOVE_DEAD = 16; // VIA RESET

  /*
    Internal animation sequence variables
    first index is the movement mode (see above),
    second index is the frame (animation lengths may vary),
    third index is the animation element.  It has three components
      [?][?][0] is the actual image tile to show
      [?][?][1] is the offset in the horizontal direction
      [?][?][2] is the offset in the vertical direction
  */
  private final static int ANIMATION[][][] = {
    // MOVE_NONE
    { {0, 0, 0} },
    // MOVE_LEFT
    { {FRM_PLAYER_WALKL1, -4, 0}, {FRM_PLAYER_WALKL0, -4, 0},
      {FRM_PLAYER_WALKL2, -4, 0}, {FRM_PLAYER_WALKL0, -4, 0},
      {FRM_PLAYER_WALKL1, -4, 0}, {FRM_PLAYER_WALKL0, -4, 0},
      {FRM_PLAYER_WALKL2, -4, 0}, {FRM_PLAYER_WALKL0, -4, 0} },
    // MOVE_RIGHT
    { {FRM_PLAYER_WALKR1, 4, 0}, {FRM_PLAYER_WALKR0, 4, 0},
      {FRM_PLAYER_WALKR2, 4, 0}, {FRM_PLAYER_WALKR0, 4, 0},
      {FRM_PLAYER_WALKR1, 4, 0}, {FRM_PLAYER_WALKR0, 4, 0},
      {FRM_PLAYER_WALKR2, 4, 0}, {FRM_PLAYER_WALKR0, 4, 0} },
    // MOVE_SLOWLEFT
    { {FRM_PLAYER_WALKL1, -4, 0}, {FRM_PLAYER_WALKL0, -4, 0}, {FRM_PLAYER_WALKL0, 0, 0},
      {FRM_PLAYER_WALKL2, -4, 0}, {FRM_PLAYER_WALKL0, -4, 0}, {FRM_PLAYER_WALKL0, 0, 0},
      {FRM_PLAYER_WALKL1, -4, 0}, {FRM_PLAYER_WALKL0, -4, 0}, {FRM_PLAYER_WALKL0, 0, 0},
      {FRM_PLAYER_WALKL2, -4, 0}, {FRM_PLAYER_WALKL0, -4, 0}, {FRM_PLAYER_WALKL0, 0, 0} },
    // MOVE_SLOWRIGHT
    { {FRM_PLAYER_WALKR1, 4, 0}, {FRM_PLAYER_WALKR0, 4, 0}, {FRM_PLAYER_WALKR0, 0, 0},
      {FRM_PLAYER_WALKR2, 4, 0}, {FRM_PLAYER_WALKR0, 4, 0}, {FRM_PLAYER_WALKR0, 0, 0},
      {FRM_PLAYER_WALKR1, 4, 0}, {FRM_PLAYER_WALKR0, 4, 0}, {FRM_PLAYER_WALKR0, 0, 0},
      {FRM_PLAYER_WALKR2, 4, 0}, {FRM_PLAYER_WALKR0, 4, 0}, {FRM_PLAYER_WALKR0, 0, 0} },
    // MOVE_CHAINLEFT
    { {FRM_PLAYER_CHAINL0, -4, 0}, {FRM_PLAYER_CHAINL1, -4, 0}, {FRM_PLAYER_CHAINL1, 0, 0},
      {FRM_PLAYER_CHAINL0, -4, 0}, {FRM_PLAYER_CHAINL2, -4, 0}, {FRM_PLAYER_CHAINL2, 0, 0},
      {FRM_PLAYER_CHAINL0, -4, 0}, {FRM_PLAYER_CHAINL1, -4, 0}, {FRM_PLAYER_CHAINL1, 0, 0},
      {FRM_PLAYER_CHAINL0, -4, 0}, {FRM_PLAYER_CHAINL2, -4, 0}, {FRM_PLAYER_CHAINL2, 0, 0} },
    // MOVE_CHAINRIGHT
    { {FRM_PLAYER_CHAINR0, 4, 0}, {FRM_PLAYER_CHAINR1, 4, 0}, {FRM_PLAYER_CHAINR1, 0, 0},
      {FRM_PLAYER_CHAINR0, 4, 0}, {FRM_PLAYER_CHAINR2, 4, 0}, {FRM_PLAYER_CHAINR2, 0, 0},
      {FRM_PLAYER_CHAINR0, 4, 0}, {FRM_PLAYER_CHAINR1, 4, 0}, {FRM_PLAYER_CHAINR1, 0, 0},
      {FRM_PLAYER_CHAINR0, 4, 0}, {FRM_PLAYER_CHAINR2, 4, 0}, {FRM_PLAYER_CHAINR2, 0, 0} },
    //  MOVE_UPLADDER = 7;
    { {FRM_PLAYER_LADDER1, 0, -4}, {FRM_PLAYER_LADDER0, 0, -4},
      {FRM_PLAYER_LADDER2, 0, -4}, {FRM_PLAYER_LADDER0, 0, -4},
      {FRM_PLAYER_LADDER1, 0, -4}, {FRM_PLAYER_LADDER0, 0, -4},
      {FRM_PLAYER_LADDER2, 0, -4}, {FRM_PLAYER_LADDER0, 0, -4} },
    //  MOVE_DOWNLADDER = 8;
    { {FRM_PLAYER_LADDER1, 0, 4}, {FRM_PLAYER_LADDER0, 0, 4},
      {FRM_PLAYER_LADDER2, 0, 4}, {FRM_PLAYER_LADDER0, 0, 4},
      {FRM_PLAYER_LADDER1, 0, 4}, {FRM_PLAYER_LADDER0, 0, 4},
      {FRM_PLAYER_LADDER2, 0, 4}, {FRM_PLAYER_LADDER0, 0, 4} },
    //  MOVE_UPROPE = 9;
    { {FRM_PLAYER_ROPE0, 0, 0},{FRM_PLAYER_ROPE1, 0, 0}, {FRM_PLAYER_ROPE2, 0, -8},
      {FRM_PLAYER_ROPE0, 0, 0},{FRM_PLAYER_ROPE1, 0, 0}, {FRM_PLAYER_ROPE2, 0, -8},
      {FRM_PLAYER_ROPE0, 0, 0},{FRM_PLAYER_ROPE1, 0, 0}, {FRM_PLAYER_ROPE2, 0, -8},
      {FRM_PLAYER_ROPE0, 0, 0},{FRM_PLAYER_ROPE1, 0, 0}, {FRM_PLAYER_ROPE2, 0, -8} },
    //  MOVE_DOWNROPE = 10;
    { {FRM_PLAYER_ROPE1, 0, 4}, {FRM_PLAYER_ROPE1, 0, 4},
      {FRM_PLAYER_ROPE1, 0, 4}, {FRM_PLAYER_ROPE1, 0, 4},
      {FRM_PLAYER_ROPE1, 0, 4}, {FRM_PLAYER_ROPE1, 0, 4},
      {FRM_PLAYER_ROPE1, 0, 4}, {FRM_PLAYER_ROPE1, 0, 4} },
    //  MOVE_FALL = 11;
    { {FRM_PLAYER_FALL0, 0, 4}, {FRM_PLAYER_FALL1, 0, 4},
      {FRM_PLAYER_FALL2, 0, 4}, {FRM_PLAYER_FALL1, 0, 4},
      {FRM_PLAYER_FALL0, 0, 4}, {FRM_PLAYER_FALL1, 0, 4},
      {FRM_PLAYER_FALL2, 0, 4}, {FRM_PLAYER_FALL1, 0, 4} },
    //  MOVE_GETHOLELEFT;
    { {FRM_PLAYER_HOLEL, 0, 0}, {FRM_PLAYER_HOLEL, 0, 0},
      {FRM_PLAYER_HOLEL, 0, 0}, {FRM_PLAYER_HOLEL, 0, 0},
      {FRM_PLAYER_HOLEL, 0, 0}, {FRM_PLAYER_HOLEL, 0, 0},
      {FRM_PLAYER_HOLEL, 0, 0}, {0, 0, 0} },
    //  MOVE_GETHOLERIGHT;
    { {FRM_PLAYER_HOLER, 0, 0}, {FRM_PLAYER_HOLER, 0, 0},
      {FRM_PLAYER_HOLER, 0, 0}, {FRM_PLAYER_HOLER, 0, 0},
      {FRM_PLAYER_HOLER, 0, 0}, {FRM_PLAYER_HOLER, 0, 0},
      {FRM_PLAYER_HOLER, 0, 0}, {0, 0, 0} },
    //  MOVE_MAKEHOLELEFT;
    { {FRM_PLAYER_HOLEL, 0, 0}, {FRM_PLAYER_HOLEL, 0, 0},
      {FRM_PLAYER_HOLEL, 0, 0}, {FRM_PLAYER_HOLEL, 0, 0},
      {FRM_PLAYER_HOLEL, 0, 0}, {FRM_PLAYER_HOLEL, 0, 0},
      {FRM_PLAYER_HOLEL, 0, 0}, {0, 0, 0} },
    //  MOVE_MAKEHOLERIGHT;
    { {FRM_PLAYER_HOLER, 0, 0}, {FRM_PLAYER_HOLER, 0, 0},
      {FRM_PLAYER_HOLER, 0, 0}, {FRM_PLAYER_HOLER, 0, 0},
      {FRM_PLAYER_HOLER, 0, 0}, {FRM_PLAYER_HOLER, 0, 0},
      {FRM_PLAYER_HOLER, 0, 0}, {0, 0, 0} },
    // MOVE_DEAD
    { {0, 0, 0}, {0, 0, 0}}
  };

// *** VARIABLES ***
  int nMoveMode;
  int nCommand;

  int nLives;

  int nCrystals, nHoles;

  int nDelay;

  int nCurFrame, nCurDelay;

  boolean bMoving, bHasExited;

  // Sound variables
  AudioClip auHole, auWin, auDie, auCrystal;

// *** CONSTRUCTORS ***
  public Player() {
    super();

    nMoveMode = 0;
    nLives = 3;
    bMoving = false;
    nDelay = 1;
    nImageID = FRM_PLAYER;
  }

  public void restartLevel() {
    --nLives;
    setCrystals(0);
    setHoles(0);
    bMoving = false;
    nCurFrame = 0;
    nImageID = FRM_PLAYER;
    bHasExited = false;
//    nCommand = 0;
  }

// *** METHODS - GET ***
  public int getHoles() {
    return nHoles;
  }

  public int getCrystals() {
    return nCrystals;
  }

  public int getLives() {
    return nLives;
  }

  public boolean hasExited() {
    return bHasExited;
  }

// *** METHODS - SET ***
  public void setSoundClips(AudioClip auH, AudioClip auW, AudioClip auD, AudioClip auC) {
    auHole = auH;
    auWin = auW;
    auDie = auD;
    auCrystal = auC;
  }

  public void setCrystals(int n) {
    nCrystals = n;
  }

  public void setHoles(int n) {
    nHoles = n;
  }

  public int addCrystal() {
    ++nCrystals;
    return nCrystals;
  }

  public int addHole() {
    ++nHoles;
    return nHoles;
  }

  public int removeHole() {
    --nHoles;
    return nHoles;
  }

// *** METHODS - ACTIONS ***
  public void moveOffset(int nOffX, int nOffY) {
    nX += nOffX;
    nY += nOffY;
  }

  /*
    Sees if the player has picked up a crystal and if the player is falling.
  */
  private void checkFall(Map map) {
    int nMX = nX / 32;
    int nMY = nY / 32;
    int nFallTile = map.getTile(nMX, nMY+1);

    if (map.levelExit.isVisible()) {
      if ((nMX == map.levelExit.getMapX()) && (nMY == map.levelExit.getMapY())){
        bHasExited = true;
        if (map.bSoundEnabled)
          auWin.play();
//        System.out.println("Level Exit!!!");
      }
    }

    Sprite c;
    for (int cntr = 0; cntr < map.vCrystals.size(); ++cntr) {
      c = (Sprite)(map.vCrystals.elementAt(cntr));
      if ( (c.isVisible()) && (c.getMapX() == nMX) && (c.getMapY() == nMY) ) {
        if (c.isCrystal()) {
          addCrystal();
          c.setVisible(false);
          if (map.bSoundEnabled)
            auCrystal.play();
//          System.out.println("Crystal " + nCrystals + " of " + map.getNumCrystals());
          if (map.getNumCrystals() <= nCrystals)
            map.levelExit.activate();
        }
        else {
          addHole();
          if (map.bSoundEnabled)
            auHole.play();
          c.setVisible(false);
        }
      }
    }

    if ( (nFallTile < 5 ) || (nFallTile == Map.TILE_CHAIN) ) {
      int nTile = map.getTile(nMX, nMY);
      if ( (nTile  < 8) || (nTile > 10) ) {
        nMoveMode = MOVE_FALL;
        bMoving = true;
      }

    }
  }

  private void doMove(Map map) {
    if (nMoveMode == MOVE_DEAD)
      return;
    ++nCurDelay;
    if (nCurDelay >= map.getDelay()) {
      nCurDelay = 0;
        nImageID = ANIMATION[nMoveMode][nCurFrame][0];
        nX += ANIMATION[nMoveMode][nCurFrame][1];
        nY += ANIMATION[nMoveMode][nCurFrame][2];
      ++nCurFrame;
      if (nCurFrame >= ANIMATION[nMoveMode].length) {
        nCurFrame = 0;
        bMoving = false;
        checkFall(map);
      }
    }
  }

  private synchronized void getHole(Map map, int x, int y) {
    Hole h;
//    System.out.println("Getting hole!");

    for (int cntr = 0; cntr < map.vHoles.size(); ++cntr) {
      h = (Hole)(map.vHoles.elementAt(cntr));
      if ( (h.isVisible()) && (h.getMapX() == x) && (h.getMapY() == y) ) {
        addHole();
        h.deactivateHole(map);
        if (map.bSoundEnabled)
          auHole.play();
      }
    }
  }

  private synchronized void setHole(Map map, int x, int y) {
    Hole h;
//    System.out.println("Setting hole!");
    for (int cntr = 0; cntr < map.vHoles.size(); ++cntr) {
      h = (Hole)(map.vHoles.elementAt(cntr));
      if (!h.isVisible()) {
        removeHole();
        h.activateHole(x*32, y*32);
        if (map.bSoundEnabled)
          auHole.play();
        break;
      }
    }
  }

/*
  private void goRight(Map map) {
  }
*/
  private synchronized void doCommand(Map map) {
    // Handle various movement commands
    int nMX = nX / 32;
    int nMY = nY / 32;
    int nTile, nTile2;

    if ((nCommand & CMD_RIGHT) != 0) {
      nTile = map.getTile(nMX+1, nMY);
      if ((nTile < 5) || (nTile > 7)) {
        if (map.getTile(nMX, nMY+1) == Map.TILE_DIRT)
          nMoveMode = MOVE_SLOWRIGHT;
        else if (nTile == Map.TILE_CHAIN)
          nMoveMode = MOVE_CHAINRIGHT;
        else
          nMoveMode = MOVE_RIGHT;
        bMoving = true;
      }
    }
    else if ((nCommand & CMD_LEFT) != 0) {
      nTile = map.getTile(nMX-1, nMY);
      if ((nTile < 5) || (nTile > 7)) {
        if (map.getTile(nMX, nMY+1) == Map.TILE_DIRT)
          nMoveMode = MOVE_SLOWLEFT;
        else if (nTile == Map.TILE_CHAIN)
          nMoveMode = MOVE_CHAINLEFT;
        else
          nMoveMode = MOVE_LEFT;
        bMoving = true;
      }
    }
    else if ((nCommand & CMD_CLIMB_UP) != 0) {
      nTile = map.getTile(nMX, nMY);
      bMoving = true;
      nTile2 = map.getTile(nMX, nMY-1);
      if ( (nTile2 < Map.TILE_BRICK) || (nTile2 > Map.TILE_STEEL) )
        if (nTile == 8)
          nMoveMode = MOVE_UPLADDER;
        else if (nTile == 9)
          nMoveMode = MOVE_UPROPE;
        else
          bMoving = false;
      else
        bMoving = false;
    }
    else if ((nCommand & CMD_CLIMB_DOWN) != 0) {
      nTile = map.getTile(nMX, nMY+1);
      if ( (nTile < 5) || (nTile > 7) ) {
        bMoving = true;
        if (nTile == 8)
          nMoveMode = MOVE_DOWNLADDER;
        else if (nTile == 9)
          nMoveMode = MOVE_DOWNROPE;
        else
          nMoveMode = MOVE_FALL;
      }
    }
    else if ((nCommand & CMD_HOLE_LEFT) != 0) {
      nTile = map.getTile(nMX-1, nMY+1);
      nMoveMode = MOVE_GETHOLELEFT;
      if (nTile == Map.TILE_HOLE)
        getHole(map, nMX-1, nMY+1);
      else {
        nTile2 = map.getTile(nMX-1, nMY);
        if ( (getHoles() > 0) && ((nTile == Map.TILE_BRICK)||(nTile == Map.TILE_DIRT)) &&
              ((nTile2 < Map.TILE_BRICK) || (nTile2 >= Map.TILE_FIRST_CLIMBABLE)))
          setHole(map, nMX-1, nMY+1);
        else
          nMoveMode = MOVE_NONE;
      }
        bMoving = true;
    }
    else if ((nCommand & CMD_HOLE_RIGHT) != 0) {
      nTile = map.getTile(nMX+1, nMY+1);
      nMoveMode = MOVE_GETHOLERIGHT;
      if (nTile == Map.TILE_HOLE)
        getHole(map, nMX+1, nMY+1);
      else {
        nTile2 = map.getTile(nMX+1, nMY);
        if ( (getHoles() > 0) && ((nTile == Map.TILE_BRICK)||(nTile == Map.TILE_DIRT)) &&
              ((nTile2 < Map.TILE_BRICK) || (nTile2 >= Map.TILE_FIRST_CLIMBABLE) ) )
          setHole(map, nMX+1, nMY+1);
        else
          nMoveMode = MOVE_NONE;
      }
        bMoving = true;
    }
    else if ((nCommand & CMD_RESTART) != 0) {
      nCommand &= ~CMD_RESTART;
      nMoveMode = MOVE_DEAD;
    }

    checkFall(map);
  }

  public boolean doUpdate(Map map) {
//    int cntr;

    if (bMoving)
      doMove(map);
    else
      doCommand(map);

    return true;
  }

  public int checkStatus(Map map) {
    if (nMoveMode == MOVE_DEAD) {
      nMoveMode = MOVE_NONE;
      return RESULT_RESET;
    }

    int nPX1 = nX - 12;
    int nPY1 = nY - 12;
    int nPX2 = nX + 12;
    int nPY2 = nY + 12;
    int nMX, nMY;
    Monster m;

    for (int cntr = 0; cntr < map.vMonsters.size(); ++cntr) {
      m = (Monster)(map.vMonsters.elementAt(cntr));
      if (m.isVisible()) {
        nMX = m.getX();
        nMY = m.getY();
        if ( (nPX1 < nMX) && (nPX2 > nMX) && (nPY1 < nMY) && (nPY2 > nMY) ) {
          if (map.bSoundEnabled)
            auDie.play();
          return RESULT_DIE;
        }
      }
    }

    return RESULT_NORMAL;
  }

  /**
    Indicates a new action.  The command variable acts like a buffer, which
    only takes effect once the current move has been completed.
  */
  public void command(int nID, boolean bAction) {
    if (nID > CMD_CHEAT)
      if (nID == CMD_CHEAT_LIFE)
        ++nLives;
    if (bAction)
      nCommand |= nID;
    else
      nCommand &= (~nID);
  }
}
